 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal;

 import java.util.List ;

 import org.eclipse.core.expressions.EvaluationContext;
 import org.eclipse.core.expressions.EvaluationResult;
 import org.eclipse.core.expressions.Expression;
 import org.eclipse.core.expressions.ExpressionConverter;
 import org.eclipse.core.expressions.IEvaluationContext;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IAdaptable;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.ISafeRunnable;
 import org.eclipse.core.runtime.SafeRunner;
 import org.eclipse.jface.action.IMenuManager;
 import org.eclipse.jface.viewers.ISelection;
 import org.eclipse.jface.viewers.ISelectionProvider;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.ui.IWorkbenchPart;
 import org.eclipse.ui.SelectionEnabler;
 import org.eclipse.ui.internal.misc.Policy;
 import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
 import org.eclipse.ui.internal.util.Util;
 import org.eclipse.ui.model.IWorkbenchAdapter;

 /**
  * This class describes the object contribution element within the popup menu
  * action registry.
  */
 public class ObjectActionContributor extends PluginActionBuilder implements
         IObjectActionContributor, IAdaptable {

     private static final String P_TRUE = "true"; //$NON-NLS-1$

     private IConfigurationElement config;

     private boolean configRead = false;

     private boolean adaptable = false;
     
     private String objectClass;

     /**
      * The constructor.
      *
      * @param config the element
      */
     public ObjectActionContributor(IConfigurationElement config) {
         this.config = config;
         this.adaptable = P_TRUE.equalsIgnoreCase(config
                 .getAttribute(IWorkbenchRegistryConstants.ATT_ADAPTABLE));
         this.objectClass = config.getAttribute(IWorkbenchRegistryConstants.ATT_OBJECTCLASS);
     }

     /* (non-Javadoc)
      * Method declared on IObjectContributor.
      */
     public boolean canAdapt() {
         return adaptable;
     }
     
     /**
      * Return the object class for this contributor.
      *
      * @return the object class
      */
     public String getObjectClass() {
         return objectClass;
     }

     /* (non-Javadoc)
      * Method declared on IObjectActionContributor.
      */
     public void contributeObjectActionIdOverrides(List actionIdOverrides) {
         if (!configRead) {
             readConfigElement();
         }

         // Easy case out if no actions
 if (currentContribution.actions != null) {
             for (int i = 0; i < currentContribution.actions.size(); i++) {
                 ActionDescriptor ad = (ActionDescriptor) currentContribution.actions
                         .get(i);
                 String id = ad.getAction().getOverrideActionId();
                 if (id != null) {
                     actionIdOverrides.add(id);
                 }
             }
         }
     }

     /**
      * Contributes actions applicable for the current selection.
      */
     public boolean contributeObjectActions(final IWorkbenchPart part,
             IMenuManager menu, ISelectionProvider selProv,
             List actionIdOverrides) {
         if (!configRead) {
             readConfigElement();
         }

         // Easy case out if no actions
 if (currentContribution.actions == null) {
             return false;
         }

         // Get a structured selection.
 ISelection sel = selProv.getSelection();
         if ((sel == null) || !(sel instanceof IStructuredSelection)) {
             return false;
         }
         IStructuredSelection ssel = (IStructuredSelection) sel;
         
         if(canAdapt()) {
            IStructuredSelection newSelection = LegacyResourceSupport.adaptSelection(ssel, getObjectClass());
            if(newSelection.size() != ssel.size()) {
                if (Policy.DEBUG_CONTRIBUTIONS) {
                 WorkbenchPlugin.log("Error adapting selection to " + getObjectClass() + //$NON-NLS-1$
 ". Contribution " + getID(config) + " is being ignored"); //$NON-NLS-1$ //$NON-NLS-2$
 }
                 return false;
            }
            ssel = newSelection;
         }
         
         final IStructuredSelection selection = ssel;
             
         // Generate menu.
 for (int i = 0; i < currentContribution.actions.size(); i++) {
             ActionDescriptor ad = (ActionDescriptor) currentContribution.actions
                     .get(i);
             if (!actionIdOverrides.contains(ad.getId())) {
                 currentContribution.contributeMenuAction(ad, menu, true);
                 // Update action for the current selection and part.
 if (ad.getAction() instanceof ObjectPluginAction) {
                     final ObjectPluginAction action = (ObjectPluginAction) ad
                             .getAction();
                     ISafeRunnable runnable = new ISafeRunnable() {
                         public void handleException(Throwable exception) {
                             WorkbenchPlugin.log("Failed to update action " //$NON-NLS-1$
 + action.getId(), exception);
                         }

                         public void run() throws Exception {
                             action.setActivePart(part);
                             action.selectionChanged(selection);
                         }
                     };
                     SafeRunner.run(runnable);
                 }
             }
         }
         return true;
     }

     /**
      * Contributes menus applicable for the current selection.
      */
     public boolean contributeObjectMenus(IMenuManager menu,
             ISelectionProvider selProv) {
         if (!configRead) {
             readConfigElement();
         }

         // Easy case out if no menus
 if (currentContribution.menus == null) {
             return false;
         }

         // Get a structured selection.
 ISelection sel = selProv.getSelection();
         if ((sel == null) || !(sel instanceof IStructuredSelection)) {
             return false;
         }

         // Generate menu.
 for (int i = 0; i < currentContribution.menus.size(); i++) {
             IConfigurationElement menuElement = (IConfigurationElement) currentContribution.menus
                     .get(i);
             currentContribution.contributeMenu(menuElement, menu, true);
         }
         return true;
     }

     /* (non-Javadoc)
      * Method declared on PluginActionBuilder.
      */
     protected ActionDescriptor createActionDescriptor(
             IConfigurationElement element) {
         return new ActionDescriptor(element, ActionDescriptor.T_POPUP);
     }

     /* (non-Javadoc)
      * Method declared on PluginActionBuilder.
      */
     protected BasicContribution createContribution() {
         return new ObjectContribution();
     }

     /**
      * Returns true if name filter is not specified for the contribution
      * or the current selection matches the filter.
      */
     public boolean isApplicableTo(Object object) {
         if (!configRead) {
             readConfigElement();
         }

         // Perform all tests with an instance of the objectClass and not
 // the actual selected object.
 if (canAdapt()) {
             Object adapted = LegacyResourceSupport.getAdapter(object, getObjectClass());
             if (adapted == null) {
                 if (Policy.DEBUG_CONTRIBUTIONS) {
                     WorkbenchPlugin
                             .log("Error adapting " + object.getClass().getName() + //$NON-NLS-1$
 " to " //$NON-NLS-1$
 + getObjectClass()
                                     + ". Contribution " + getID(config) + " is being ignored"); //$NON-NLS-1$ //$NON-NLS-2$
 }
             } else {
                 object = adapted;
             }
         }
             
         if (!testName(object)) {
             return false;
         }

         return ((ObjectContribution) currentContribution)
                 .isApplicableTo(object);
     }

     /**
      * Reads the configuration element and all the children.
      * This creates an action descriptor for every action in the extension.
      */
     private void readConfigElement() {
         currentContribution = createContribution();
         readElementChildren(config);
         configRead = true;
     }

     /* (non-Javadoc)
      * Method declared on PluginActionBuilder.
      */
     protected boolean readElement(IConfigurationElement element) {
         String tag = element.getName();

         // Found visibility sub-element
 if (tag.equals(IWorkbenchRegistryConstants.TAG_VISIBILITY)) {
             ((ObjectContribution) currentContribution)
                     .setVisibilityTest(element);
             return true;
         }

         // Found filter sub-element
 if (tag.equals(IWorkbenchRegistryConstants.TAG_FILTER)) {
             ((ObjectContribution) currentContribution).addFilterTest(element);
             return true;
         }

         if (tag.equals(IWorkbenchRegistryConstants.TAG_ENABLEMENT)) {
             ((ObjectContribution) currentContribution)
                     .setEnablementTest(element);
             return true;
         }

         return super.readElement(element);
     }

     /**
      * Returns whether the current selection matches the contribution name filter.
      */
     private boolean testName(Object object) {
         String nameFilter = config.getAttribute(IWorkbenchRegistryConstants.ATT_NAME_FILTER);
         if (nameFilter == null) {
             return true;
         }
         String objectName = null;
         IWorkbenchAdapter de = (IWorkbenchAdapter)Util.getAdapter(object, IWorkbenchAdapter.class);
         if (de != null) {
             objectName = de.getLabel(object);
         }
         if (objectName == null) {
             objectName = object.toString();
         }
         return SelectionEnabler.verifyNameMatch(objectName, nameFilter);
     }

     /**
      * Helper class to collect the menus and actions defined within a
      * contribution element.
      */
     private static class ObjectContribution extends BasicContribution {
         private ObjectFilterTest filterTest;

         private ActionExpression visibilityTest;

         private Expression enablement;

         /**
          * Add a filter test.
          *
          * @param element the element
          */
         public void addFilterTest(IConfigurationElement element) {
             if (filterTest == null) {
                 filterTest = new ObjectFilterTest();
             }
             filterTest.addFilterElement(element);
         }

         /**
          * Set the visibility test.
          *
          * @param element the element
          */
         public void setVisibilityTest(IConfigurationElement element) {
             visibilityTest = new ActionExpression(element);
         }

         /**
          * Set the enablement test.
          *
          * @param element the element
          */
         public void setEnablementTest(IConfigurationElement element) {
             try {
                 enablement = ExpressionConverter.getDefault().perform(element);
             } catch (CoreException e) {
                 WorkbenchPlugin.log(e);
             }
         }

         /**
          * Returns true if name filter is not specified for the contribution
          * or the current selection matches the filter.
          *
          * @param object the object to test
          * @return whether we're applicable
          */
         public boolean isApplicableTo(Object object) {
             boolean result = true;
             if (visibilityTest != null) {
                 result = result && visibilityTest.isEnabledFor(object);
                 if (!result) {
                     return result;
                 }
             } else if (filterTest != null) {
                 result = result && filterTest.matches(object, true);
                 if (!result) {
                     return result;
                 }
             }
             if (enablement != null) {
                 try {
                     IEvaluationContext context = new EvaluationContext(null,
                             object);
                     context.setAllowPluginActivation(true);
                     context.addVariable("selection", object); //$NON-NLS-1$
 EvaluationResult evalResult = enablement.evaluate(context);
                     if (evalResult == EvaluationResult.FALSE) {
                         return false;
                     }
                 } catch (CoreException e) {
                     enablement = null;
                     WorkbenchPlugin.log(e);
                     result = false;
                 }
             }
             return result;
         }
     }
     
     /**
      * Debugging helper that will print out the contribution names for this
      * contributor.
      */
     public String toString() {
         StringBuffer buffer = new StringBuffer ();
         IConfigurationElement[] children = config.getChildren();
         for (int i = 0; i < children.length; i++) {
             IConfigurationElement element = children[i];
             String label = element.getAttribute(IWorkbenchRegistryConstants.ATT_LABEL);
             if(label != null) {
                 buffer.append(label);
                 buffer.append('\n');
             }
         }
         return buffer.toString();
     }

     /* (non-Javadoc)
      * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
      */
     public Object getAdapter(Class adapter) {
         if (adapter.equals(IConfigurationElement.class)) {
             return config;
         }
         return null;
     }
 }

